package cn.xiaoke.damowaong;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;

/**
 * @author xiaokedamowang
 * @date 2022/8/10 7:42
 */
public class MyCollectionUtil {


    public static <T> List<T> list(List<T> ts) {
        return List.copyOf(ts);
    }

    public static <T> List<T> list2(List<T> ts) {
        return Collections.unmodifiableList(ts);
    }

    @SafeVarargs
    public static <T> List<T> list(T... t) {
        return List.of(Arrays.copyOf(t, t.length));
    }

    public static <T> List<T> list2(T[] tarr) {
        return Collections.unmodifiableList(Arrays.asList(tarr));
    }


    public static <T> T head(List<T> list) {
        if (list.size() == 0) {
            throw new IllegalStateException("head of empty list");
        }
        return list.get(0);
    }

    public static <T> List<T> tail(List<T> list) {
        if (list.size() == 0) {
            throw new IllegalStateException("tail of empty list");
        }
        List<T> workList = copy(list);
        workList.remove(0);
        return Collections.unmodifiableList(workList);
    }


    public static Integer fold(List<Integer> is, Integer identity, Function<Integer, Function<Integer, Integer>> f) {
        int result = identity;
        for (Integer i : is) {
            result = f.apply(result).apply(i);
        }
        return result;
    }


    public static <T, U> U foldRight(List<T> ts, U identity, Function<T, Function<U, U>> f) {
        U result = identity;
        for (int i = ts.size(); i > 0; i--) {
            result = f.apply(ts.get(i - 1)).apply(result);
        }
        return result;
    }

    public static <T, U> U foldRightRecursive(List<T> ts, U identity, Function<T, Function<U, U>> f) {
        return ts.isEmpty() ? identity : f.apply(head(ts)).apply(foldRightRecursive(tail(ts), identity, f));
    }

    public static <T, U> U foldRightRec(U acc, List<T> ts, U identity, Function<T, Function<U, U>> f) {
        return ts.isEmpty() ? acc : foldRightRec(f.apply(head(ts)).apply(acc), tail(ts), identity, f);
    }

    public static <T, U> U foldRightRec2(List<T> ts, U identity, Function<T, Function<U, U>> f) {
        return foldRightRec2Tail(identity, reverse(ts), f).eval();
    }

    private static <T, U> TailCall<U> foldRightRec2Tail(U acc, List<T> ts, Function<T, Function<U, U>> f) {
        return ts.isEmpty() ? TailCall.ret(acc) : TailCall.sus(() -> foldRightRec2Tail(f.apply(head(ts)).apply(acc), tail(ts), f));
    }

    public static <T> List<T> reverse(List<T> list) {
        List<T> result = new ArrayList<T>();
        for (int i = list.size() - 1; i >= 0; i--) {
            result.add(list.get(i));
        }
        return Collections.unmodifiableList(result);
    }

    public static <T> List<T> list() {
        return Collections.emptyList();
    }

    public static <T> List<T> list(T t) {
        return Collections.singletonList(t);
    }

    private static <T> List<T> copy(List<T> ts) {
        return new ArrayList<>(ts);
    }

    public static <T> List<T> append(List<T> list, T t) {
        List<T> ts = copy(list);
        ts.add(t);
        return Collections.unmodifiableList(ts);
    }

    public static <T, U> U foldLeft(List<T> ts, U identity, Function<U, Function<T, U>> f) {
        U result = identity;
        for (T t : ts) {
            result = f.apply(result).apply(t);
        }
        return result;
    }

    public static <T, U> U foldLeftRecursive(List<T> ts, U identity, Function<U, Function<T, U>> f) {
        return ts.isEmpty() ? identity : foldLeftRecursive(tail(ts), f.apply(identity).apply(head(ts)), f);
    }

    public static <T> List<T> prepend(T t, List<T> list) {
        return foldLeft(list, list(t), a -> b -> append(a, b));
    }

    public static <T> List<T> reverse2(List<T> list) {
        return foldLeft(list, list(), x -> y -> prepend(y, x));
    }

    public static <T, U> List<U> mapViaFoldLeft(List<T> list, Function<T, U> f) {
        return foldLeft(list, list(), x -> y -> append(x, f.apply(y)));
    }


    public static <T, U> List<U> mapViaFoldRight(List<T> list, Function<T, U> f) {
        return foldRight(list, list(), x -> y -> prepend(f.apply(x), y));
    }

    public static <T> void forEach(Collection<T> ts, Effect<T> e) {
        for (T t : ts) {
            e.accept(t);
        }
    }

    public static List<Integer> range(int start, int end) {
        return unfold(start, x -> x + 1, x -> x < end);
    }

    public static <T> List<T> unfold(T seed, Function<T, T> f, Function<T, Boolean> p) {
        List<T> result = new ArrayList<>();
        T temp = seed;
        while (p.apply(temp)) {
            result = MyCollectionUtil.append(result, temp);
            temp = f.apply(temp);
        }
        return result;
    }

    public static List<Integer> range2(Integer start, Integer end) {
        return end <= start ? MyCollectionUtil.list()
                : MyCollectionUtil.prepend(start, range2(start + 1, end));
    }

    public static List<Integer> range3(List<Integer> acc, Integer start, Integer end) {
        return end <= start ? acc : range3(append(acc, start), start + 1, end);
    }
}

